
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

namespace Derm.BindGen
{
	/// <summary>
	/// Specification dictionary.
	/// </summary>
	class SpecDictionary
	{
		#region Singleton Pattern

		public SpecDictionary()
		{
			sSpecDictionary = this;
		}

		public static SpecDictionary Instance
		{
			get {
				return (sSpecDictionary);
			}
		}

		private static SpecDictionary sSpecDictionary = null;

		#endregion

		#region Extension Dictionary

		public void ParseExtensionList(string path)
		{
			StreamReader sr = new StreamReader(path);
			string sLine;

			while (sr.EndOfStream == false) {
				// Read next line
				sLine = sr.ReadLine();
				sLine = sLine.Trim();

				// Skip empty lines
				if (String.IsNullOrEmpty(sLine) == true)
					continue;
				// Skip comments
				if (sLine[0] == '#')
					continue;

				// Add extension
				mExtensionList.AddLast(sLine);
			}

			sr.Close();

			Console.WriteLine("Registered {0} extensions.", mExtensionList.Count);
		}

		public string EndsWithExtension(string word)
		{
			foreach (string e in mExtensionList)
				if (word.EndsWith(e) == true)
					return (e);

			return (null);
		}

		/// <summary>
		/// Dictionary extensions.
		/// </summary>
		public LinkedList<string> Extensions
		{
			get { return (mExtensionList); }
		}

		/// <summary>
		/// Known extension strings.
		/// </summary>
		private LinkedList<string> mExtensionList = new LinkedList<string>();

		#endregion

		#region Suffixed Dictionary

		public void ParseSuffixesList(string path)
		{
			StreamReader sr = new StreamReader(path);
			string sLine;

			while (sr.EndOfStream == false) {
				// Read next line
				sLine = sr.ReadLine();
				sLine = sLine.Trim();

				// Skip empty lines
				if (String.IsNullOrEmpty(sLine) == true)
					continue;
				// Skip comments
				if (sLine[0] == '#')
					continue;

				// Add suffix
				mSuffixList.AddLast(sLine);
			}

			sr.Close();

			Console.WriteLine("Registered {0} suffixes.", mSuffixList.Count);
		}

		public string EndsWithSuffix(string word)
		{
			foreach (string s in mSuffixList)
				if (word.EndsWith(s) == true)
					return (s);

			return (null);
		}

		public bool IsSuffix(string word)
		{
			return (mSuffixList.Contains(word));
		}

		/// <summary>
		/// Known suffix strings.
		/// </summary>
		private LinkedList<string> mSuffixList = new LinkedList<string>();

		#endregion

		#region Word Dictionary

		public void ParseSpec(SpecParser sParser, string sWordFile)
		{
			#region Retroaction

			// Use pre-existing word list
			if (File.Exists(sWordFile) == true) {
				// Load pre-existing word file
				ParseWordList(sWordFile);
				// Backup it, it will be overwritten
				File.Copy(sWordFile, sWordFile+".bkp", true);
			}

			#endregion

			#region Word Parsing From Specification

			// Backup current word list
			LinkedList<string> sOldWords = mWordList;

			mWordList = new LinkedList<string>();
			foreach (KeyValuePair<string, Function> f in sParser.Functs) {
				string word, ext, fName = f.Value.ImplementationName;
				int fWordStart = 0;

				// Remove extension suffix
				if ((ext = EndsWithExtension(fName)) != null)
					fName = fName.Substring(0, fName.Length-ext.Length);

				// Split function words using upper case letters
				for (int i = 1; i < fName.Length-1; i++) {
					if ((IsWordSeparator(fName[i]) == true) && (IsWordSeparator(fName[i+1]) == false)) {
						word = fName.Substring(fWordStart, i-fWordStart);

						if (mWordList.Contains(word) == false)
							mWordList.AddLast(word);
						fWordStart = i;
					}
				}
				if (fWordStart < fName.Length) {
					word = fName.Substring(fWordStart, fName.Length-fWordStart);

					if (mWordList.Contains(word) == false)
						mWordList.AddLast(word);
				}
			}

			#endregion

			#region Reduce Suffixes

			LinkedList<string> sNoSuffixWords = new LinkedList<string>(mWordList);	// Copy current dictionary

			// Note: this code region aim to automatically reduce words with suffixes.
			// This code works only if the SpecDictionary has already parsed a word
			// dictionary file.
			// 
			// Supposing that the world dictionary file doesn't contains words with suffixes,
			// we can determine whether a word should be inserted "as is", or it shall be
			// removed from dictionary since it has a suffix.
			//
			// In the case a parsed word has a suffix, determine the word without suffix; if
			// the processed word is already listed in dictionary, the original word shall be
			// removed from dictionary, otherwise not.

			foreach (string s in mWordList) {
				string sSuffix = null;

				// Determinate suffix remotion candidate word!
				if ((sSuffix = EndsWithSuffix(s)) != null) {
					// Remove suffix from word
					string word = s.Substring(0, s.Length-sSuffix.Length);
					// Remove repeated word?
					if ((sOldWords.Contains(word) == true) && (sOldWords.Contains(s) == false))
						sNoSuffixWords.Remove(s);
				}
			}
			mWordList = sNoSuffixWords;

			#endregion

			#region Merge Existing Words

			foreach (string s in sOldWords) {
				if (mWordList.Contains(s) == false)
					mWordList.AddLast(s);
			}

			#endregion

			#region Store World List

#if false

			// Store words into file
			StreamWriter sw = new StreamWriter(sWordFile);
			SortedList<string, string> sSortedWords = new SortedList<string,string>(new SpecWordComparer());

			foreach (string w in mWordList)
				sSortedWords.Add(w, w);

			sw.WriteLine("#");
			sw.WriteLine("# OpenGL words");
			sw.WriteLine("#");
			sw.WriteLine();

			foreach (KeyValuePair<string, string> w in sSortedWords)
				sw.WriteLine(w.Value);

			sw.Close();

			Console.WriteLine("Store {0} words.", sSortedWords.Count);

			// Reparse word list file
			//ParseWordList(sWordFile);
#endif

			#endregion
		}

		private bool IsWordSeparator(char c)
		{
			return ((c >= 'A')&& (c < 'Z'));
		}

		/// <summary>
		/// Word list comparer.
		/// </summary>
		/// <remarks>
		/// This class order in a proper way the word list. It sorts alphabetically
		/// the words contained in the dictionary, but in the case two words overlap,
		/// the longer word has a precedence respect the shorter word.
		/// </remarks>
		private class SpecWordComparer : IComparer<string>
		{
			public int Compare(string x, string y)
			{
				bool wOverlap = x.StartsWith(y) || y.StartsWith(x);

				if (wOverlap == true) {
					if (x != y) {
						if (x.Length < y.Length)
							return (+1);
						else
							return (-1);
					} else
						return (0);
				} else
					return (x.CompareTo(y));
			}
		}

		public void ParseWordList(string path)
		{
			StreamReader sr = new StreamReader(path);
			string sLine;

			mWordList.Clear();
			while (sr.EndOfStream == false) {
				// Read next line
				sLine = sr.ReadLine();
				sLine = sLine.Trim();

				// Skip empty lines
				if (String.IsNullOrEmpty(sLine) == true)
					continue;
				// Skip comments
				if (sLine[0] == '#')
					continue;

				// Performe some checks
				if (mWordList.Count > 0) {
					if (sLine.StartsWith(mWordList.Last.Value) == true)
						throw new Exception("Words list not correctly ordered.");
				}
				
				// Add word
				mWordList.AddLast(sLine);
			}

			sr.Close();
		}

		public LinkedList<string> WordList
		{
			get { return (mWordList); }
		}

		/// <summary>
		/// Known token strings.
		/// </summary>
		private LinkedList<string> mWordList = new LinkedList<string>();

		#endregion

		#region Controlled Function Naming

		/// <summary>
		/// Store a function name map for reference.
		/// </summary>
		/// <param name="sParser">
		/// A <see cref="SpecParser"/>
		/// </param>
		/// <param name="sFuncFile">
		/// A <see cref="System.String"/>
		/// </param>
		public void ParseFunctionNames(SpecParser sParser, string sFuncFile)
		{
			StreamWriter sw = new StreamWriter(sFuncFile);
			SortedDictionary<string, Function> sFuncts = new SortedDictionary<string,Function>(sParser.Functs);

			foreach (KeyValuePair<string, Function> f in sFuncts) {
				string fName = f.Value.ImportName;
				string nCanonical = f.Value.ImportName, nOverloaded = RemoveSuffixes(f.Value.ImplementationName);
				string tabs = "";
				int tabsCount;

				// Compute tabs count
				tabsCount = nCanonical.Length + 2;		// Line characters
				tabsCount = (tabsCount - (tabsCount % 4));
				tabsCount = (80 - tabsCount) / 4;
				// Tabs
				for (int i = 0; i < tabsCount; i++)
					tabs += "	";

				sw.WriteLine(nCanonical + " =" + tabs + nOverloaded);
			}

			sw.Close();
		}

		/// <summary>
		/// Remove every suffix present on name. 
		/// </summary>
		/// <param name="name">
		/// A <see cref="System.String"/> specifying the function name.
		/// </param>
		/// <returns>
		/// A <see cref="System.String"/> which represent the string passed as parameter
		/// without known suffixes.
		/// </returns>
		public string RemoveSuffixes(string name)
		{
			string sName = name;

			if (WordList.Count > 0) {
				string ext = null;

				if ((ext = EndsWithExtension(sName)) != null)
					sName = sName.Substring(0, sName.Length-ext.Length);

				for(;;) {
					bool bStartWithWord = false;

					foreach (string w in WordList) {
						if (sName.StartsWith(w) == true) {
							sName = sName.Substring(w.Length);
							bStartWithWord = true;
							break;
						}
					}

					if (bStartWithWord == false)
						break;
				}

				if (sName.Length > 0) {
					if (IsSuffix(sName) == false) {
						return (name);
					} else
						return (name.Substring(0, name.Length-sName.Length));
				} else
					return (name);
			} else
				return (name);
		}

		#endregion

	}
}
